﻿using Common.DataAnnotations;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using System.Linq;

namespace Common.Extension
{
    /// <summary>
    /// 枚举扩展方法
    /// </summary>
    public static class EnumExtensions
    {
        /// <summary>
        /// 获取枚举字符串值，支持位标志
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static string GetStringValue(this Enum type)
        {
            var descriptor = type.GetDescriptor();
            return descriptor.StringValue ?? descriptor.Name;
        }

        /// <summary>
        /// 获取枚举描述，支持位标志
        /// </summary>
        /// <param name="type"></param>
        /// <param name="scene">场景</param>
        /// <returns></returns>
        public static string GetDescription(this Enum type, string scene)
        {
            var descriptor = type.GetDescriptor();
            return descriptor.GetDescription(scene);
        }
        /// <summary>
        /// 获取枚举描述，支持位标志，default场景或空场景
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static string GetDescription(this Enum type)
        {
            var descriptor = type.GetDescriptor();
            return descriptor.GetDescription(null);
        }
        /// <summary>
        /// 获取枚举描述，支持位标志合并返回
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static EnumDescriptor GetDescriptor(this Enum type)
        {
            var enumNumber = Convert.ToInt32(type);
            Type typeOfEnum = type.GetType();
            return GetDescriptor(typeOfEnum, enumNumber);
        }
        private static EnumDescriptor GetDescriptor(Type enumType, int enumVal)
        {
            var descriptors = EnumDescriptor.GetDescriptors(enumType);
            var des = descriptors.Find(d => d.Value == enumVal);
            if (des == null)
            {
                var matches = descriptors.Where(x => (x.Value & enumVal) == x.Value);
                des = new EnumDescriptor();
                des.Value = enumVal;
                des.Name = matches.Join(x => x.Name, ',');
                des.DisplayText = matches.Join(x => x.DisplayText, ',');
                des.StringValue = matches.Join(x => x.StringValue, ',');
                var allKeys = matches.SelectMany(x => x.Descriptions.Keys).Distinct();
                foreach (var item in allKeys)
                {
                    des.Descriptions[item] = string.Join(",", matches.Select(x => x.Descriptions.TryGetValue(item, out string val) ? val : x.Name));
                }
            }
            return des;
        }
        /// <summary>
        /// 获取枚举描述，支持位标志合并返回
        /// </summary>
        /// <param name="enumVal"></param>
        /// <returns>return <see cref="EnumDescriptor"/>  when enumVal is an enum instance, otherwise null</returns>
        public static EnumDescriptor ToDescriptor(this object enumVal)
        {
            if (enumVal == null && !enumVal.GetType().IsEnum())
            {
                return null;
            }
            return GetDescriptor(enumVal.GetType(), Convert.ToInt32(enumVal));
        }
        /// <summary>
        /// 转换为枚举
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="s"></param>
        /// <param name="ignoreCase"></param>
        /// <returns></returns>
        public static T ToEnum<T>(this string s, bool ignoreCase = false)
        {
            return EnumDescriptor.ParseTo<T>(s, ignoreCase);
        }
        /// <summary>
        /// 尝试转换为枚举，区分大小写
        /// </summary>
        /// <typeparam name="T">必须是枚举类型或可空枚举类型</typeparam>
        /// <param name="s"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public static bool TryToEnum<T>(this string s, out T value)
        {
            return EnumDescriptor.TryParseTo<T>(s, false, out value);
        }
        /// <summary>
        /// 尝试转换为枚举
        /// </summary>
        /// <typeparam name="T">必须是枚举类型或可空枚举类型</typeparam>
        /// <param name="s"></param>
        /// <param name="ignoreCase"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public static bool TryToEnum<T>(this string s, bool ignoreCase, out T value)
        {
            return EnumDescriptor.TryParseTo<T>(s, ignoreCase, out value);
        }
        /// <summary>
        /// 拆分枚举项
        /// </summary>
        /// <typeparam name="T">枚举类型</typeparam>
        /// <param name="s"></param>
        /// <exception cref="ArgumentException">T必须枚举类型，T枚举类型必须包含Flag标签</exception>
        /// <returns>返回拆分后的枚举，若无法拆分（枚举无对应定义的项），将枚举实例放入集合返回</returns>
        public static List<T> Split<T>(this T s) where T : Enum
        {
            Type typeOfT = typeof(T);
            if (!typeOfT.IsEnum && typeOfT.GetGenericTypeDefinition() == typeof(Nullable<>) && !Nullable.GetUnderlyingType(typeOfT).IsEnum)
            {
                throw new ArgumentException("参数必须是枚举类型");
            }
            if (typeOfT.GetCustomAttribute<FlagsAttribute>() == null)
            {
                throw new ArgumentException("参数必须是带标志位的枚举类型");
            }
            var types = new List<T>();
            int pType = Convert.ToInt32(s);
            var values = Enum.GetValues(typeOfT);
            foreach (var v in values)
            {
                int item = Convert.ToInt32(v);

                if ((item & pType) == item)
                {
                    types.Add((T)Enum.Parse(typeOfT, item.ToString()));
                }
            }
            if (!types.Any())
            {
                types.Add(s);
            }
            return types;
        }
        /// <summary>
        /// 获取枚举描述
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="enumItem"></param>
        /// <param name="ignoreCase"></param>
        /// <param name="scene"></param>
        /// <returns></returns>
        public static string GetEnumDescription<T>(this string enumItem, bool ignoreCase = false, string scene = null) where T : Enum
        {
            if (string.IsNullOrEmpty(enumItem))
            {
                return null;
            }
            string[] arr = enumItem.Split(',');
            var matches = new List<EnumDescriptor>();
            foreach (var item in arr)
            {
                var des = item.ToEnum<T>(ignoreCase).GetDescriptor();
                if (des != null)
                {
                    matches.Add(des);
                }
            }
            if (!matches.Any())
            {
                return string.Empty;
            }
            if (scene == null || scene == "default")
            {
                return string.Join(", ", matches.Select(x => x.DisplayText ?? x.StringValue ?? x.Name));
            }
            List<string> res = new List<string>();
            foreach (var m in matches)
            {
                res.Add(m.Descriptions.TryGetValue(scene, out string val) ? val : (m.DisplayText ?? m.StringValue ?? m.Name));
            }
            return string.Join(", ", res.Distinct());
        }
    }
}
